package model;

/**
 * Class that represents all current auctions
 * and logged-in-users as a singelton.
 *
 * @version 1.0 vom 16.10.2012
 * @author Peter Tillian 1026312
 */

import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import java.util.Timer;
import java.util.concurrent.CopyOnWriteArrayList;

import security.channel.Channel;

public class ObjectManagementPool {
	// only instance of this AuctionPool
	private static ObjectManagementPool instance = null;
	private List<Auction> currentAuctions;
	private List<Auction> expiredAuctions;
	private List<User> currentUsers;
	private List<Timer> auctionTimers;
	private List<GroupBidAuctionEntry> groupBidAuctions;

	// private Constructor: cannot be instantiated from outside.
	private ObjectManagementPool() {
		this.currentAuctions = Collections
				.synchronizedList(new CopyOnWriteArrayList<Auction>());
		this.expiredAuctions = Collections
				.synchronizedList(new CopyOnWriteArrayList<Auction>());
		this.currentUsers = Collections
				.synchronizedList(new CopyOnWriteArrayList<User>());
		this.auctionTimers = Collections
				.synchronizedList(new CopyOnWriteArrayList<Timer>());
		this.groupBidAuctions = Collections
				.synchronizedList(new CopyOnWriteArrayList<GroupBidAuctionEntry>());
	}

	public static ObjectManagementPool getInstance() {
		synchronized (ObjectManagementPool.class) {
			if (instance == null) {
				instance = new ObjectManagementPool();
			}
		}
		return instance;
	}

	/**
	 * Adds an auction to the current auctions. The auctions-List is
	 * synchronized, so it is sure, that the insert of a new auction into the
	 * auctions-List is atomic.
	 */
	public boolean addAuction(Auction a) {
		return this.currentAuctions.add(a);
	}

	public synchronized void expireAuction(Auction a) {
		this.currentAuctions.remove(a);
		this.expiredAuctions.add(a);
		ArrayList<GroupBidAuctionEntry> listToReject = new ArrayList<GroupBidAuctionEntry>();
		for (GroupBidAuctionEntry entry : this.groupBidAuctions) {
			if (entry.getAuction().equals(a)) {
				if (entry.getConfirmer1Channel() != null) {
					entry.getConfirmer1Channel()
							.println(
									"!rejected The auction expired before enough confirms where given.");
				}
				if (entry.getConfirmer2Channel() != null) {
					entry.getConfirmer2Channel()
							.println(
									"!rejected The auction expired before enough confirms where given.");
				}
				listToReject.add(entry);
			}
		}
		for (GroupBidAuctionEntry entry : listToReject) {
			this.groupBidAuctions.remove(entry);
		}
	}

	/**
	 * Adds an auction-timer to the current auctionTimers-list. The
	 * auctionTimers-List is synchronized, so it is sure, that the insert of a
	 * new auctionTimer is atomic.
	 */
	public boolean addAuctionTimer(Timer t) {
		return this.auctionTimers.add(t);
	}

	/**
	 * Cancels all running auction-timers.
	 */
	public void cancelAllTimers() {
		for (Timer t : this.auctionTimers) {
			t.cancel();
		}
	}

	/**
	 * Adds and or logins a user to the actual loggedIn-Users. Method is
	 * synchronized, so it is sure, that the insert of a new user into the
	 * loggedIn-User-List is atomic.
	 */
	public synchronized boolean loginUser(User newUser) {
		for (User u : this.currentUsers) {
			if (newUser.isIdentic(u)) {
				if (!u.isLoggedIn()) {
					u.login();
					return true;
				} else {
					return false;
				}
			}
		}
		// if user is not already in the currentUsers-list add it to that list.
		return this.currentUsers.add(newUser);
	}

	public List<User> getLoggedInUsers() {
		ArrayList<User> result = new ArrayList<User>();
		for (User user : this.currentUsers) {
			if (user.isLoggedIn()) {
				result.add(user);
			}
		}
		return result;
	}

	public List<User> getCurrentUsers() {
		return this.currentUsers;
	}

	public List<Auction> getCurrentAuctions() {
		return this.currentAuctions;
	}

	public List<Auction> getExpiredAuctions() {
		return this.expiredAuctions;
	}

	/**
	 * This method returns the user-object from the current logged-in-users of
	 * the AuctionPool with the specific name. Null otherwise.
	 */
	public User getCurrentUserByName(String name) {
		for (User u : ObjectManagementPool.getInstance().getCurrentUsers()) {
			if (u.getUsername().equals(name)) {
				return u;
			}
		}
		return null;
	}

	/**
	 * This method returns the auction-object from the current auctions of the
	 * AuctionPool with the specific id. Null otherwise.
	 */
	public Auction getCurrentAuctionById(long id) {
		for (Auction a : this.currentAuctions) {
			if (a.getId() == id) {
				return a;
			}
		}
		return null;
	}

	/**
	 * This method checks if the specified id is an id of an actual auction.
	 * Therefore the Auctian with that id must not be expired and not canceled.
	 */
	public boolean isAuctionActual(long id) {
		return this.getCurrentAuctionById(id) != null;
	}

	/**
	 * This method lists all actual auctions. The auctions endtime must be in
	 * future and the auction must not be cancelled.
	 */
	public String listActualAuctions() {
		String allActualAuctions = "";
		int counter = 0;
		for (Auction a : currentAuctions) {
			if (counter++ == 0)
				allActualAuctions = a.toString();
			else
				allActualAuctions += "={()?!" + a.toString();
		}
		if (allActualAuctions.isEmpty())
			return "There's actual no auction.";
		else
			return allActualAuctions;
	}

	public String addToGroupBidAuctions(Auction auction, User initiator,
			double amount, Channel channel) {
		if (this.currentAuctions.contains(auction)) {
			if (!(this.getNumOfAuctionsWithGroupBids().size() < this
					.getLoggedInUsers().size())
					&& !this.getNumOfAuctionsWithGroupBids().contains(auction)) {
				initiator.addRejectedGroupBid();
				return "the number of auctions with groupbids would be higher than the number of logged in users.";
			}
			for (GroupBidAuctionEntry entry : this.groupBidAuctions) {
				if (entry.getAuction().equals(auction)
						&& entry.getInitiator().equals(initiator)
						&& entry.getAmount() == amount) {
					return "you already started an equal groupbid for this auction.";
				}
			}
			if (this.getNumOfUsersWithHigherStarvationRisk(initiator) > 0
					&& this.getNumOfUsersWithHigherStarvationRisk(initiator) >= (this
							.getLoggedInUsers().size() - this
							.getNumOfAuctionsWithGroupBids().size())) {
				initiator.addRejectedGroupBid();
				return "if it was placed other users would probably be at risk of starvation.";
			}
			this.groupBidAuctions.add(new GroupBidAuctionEntry(auction,
					initiator, amount));
			initiator.resetRejectedGroupBids();
			return "placed";
		}
		return "the auction does not exist or is already expired.";
	}

	public boolean confirmGroupBidAuction(User confirmer,
			Channel confirmerChannel, long auctionID, double amount,
			String username) {
		GroupBidAuctionEntry toConfirm = null;
		for (GroupBidAuctionEntry entry : this.groupBidAuctions) {
			if (entry.getAuction().getId() == auctionID
					&& entry.getAmount() == amount
					&& entry.getInitiator().getUsername().toLowerCase()
							.equals(username.toLowerCase())) {
				toConfirm = entry;
				break;
			}
		}
		if (toConfirm != null) {
			return toConfirm.addConfirm(confirmer, confirmerChannel);
		} else {
			confirmerChannel.println("!rejected An auction with id "
					+ auctionID + " and a started groupBid from user "
					+ username + " with a groupBid amount of " + amount
					+ " does not exist.");
		}
		return false;
	}

	public List<GroupBidAuctionEntry> getGroupBidAuctions() {
		return this.groupBidAuctions;
	}

	public List<Auction> getNumOfAuctionsWithGroupBids() {
		ArrayList<Auction> auctions = new ArrayList<Auction>();
		for (GroupBidAuctionEntry entry : this.groupBidAuctions) {
			if (!auctions.contains(entry.getAuction())) {
				auctions.add(entry.getAuction());
			}
		}
		return auctions;
	}

	public int getNumOfUsersWithHigherStarvationRisk(User compare) {
		int counter = 0;
		for (User user : this.getLoggedInUsers()) {
			if (user.getRejectedGroupBids() - 1 > compare.getRejectedGroupBids()) {
				counter++;
			}
		}
		return counter;
	}

	public class GroupBidAuctionEntry {
		private Auction auction;
		private User initiator;
		private short confirms;
		private User confirmer1;
		private User confirmer2;
		private double amount;
		private Channel confirmer1Channel;
		private Channel confirmer2Channel;

		public GroupBidAuctionEntry(Auction auction, User initiator,
				double amount) {
			this.auction = auction;
			this.initiator = initiator;
			this.confirms = 0;
			this.amount = amount;
		}

		public synchronized boolean addConfirm(User confirmer, Channel channel) {
			if (confirmer.getUsername().equals(initiator.getUsername())) {
				channel.println("!rejected You can not confirm a groupbid that you started.");
				return false;
			}
			if (!ConfirmMonitor.getInstance().grantConfirm(confirmer, this)) {
				channel.println("!rejected Your confirm could probably cause a deadlock.");
				return false;
			}
			if (confirmer1 == null) {
				confirmer1 = confirmer;
				confirms++;
				this.confirmer1Channel = channel;
				ConfirmMonitor.getInstance().addBlockedUser(confirmer1);
				return true;
			} else if (confirmer2 == null) {
				confirmer2 = confirmer;
				confirms++;
				this.confirmer2Channel = channel;
				if (this.auction.bid(this.amount, this.initiator)) {
					ConfirmMonitor.getInstance().removeBlockedUser(confirmer1);
					this.confirmer1Channel.println("!confirmed");
					this.confirmer2Channel.println("!confirmed");
				} else {
					this.confirmer1Channel
							.println("!rejected Please use a higher bid price.");
					this.confirmer2Channel
							.println("!rejected Please use a higher bid price.");
				}
				groupBidAuctions.remove(this);
				return true;
			}
			channel.println("!rejected GroupBid was already confirmed twice.");
			return false;
		}

		public User getInitiator() {
			return this.initiator;
		}

		public Auction getAuction() {
			return this.auction;
		}

		public short getConfirmNum() {
			return this.confirms;
		}

		public User getConfirmer1() {
			return this.confirmer1;
		}

		public User getConfirmer2() {
			return this.confirmer2;
		}

		public double getAmount() {
			return this.amount;
		}

		public Channel getConfirmer1Channel() {
			return this.confirmer1Channel;
		}

		public Channel getConfirmer2Channel() {
			return this.confirmer2Channel;
		}
	}
} // end of class AuctionPool
